package com.lmx.persistence.service;

import io.katharsis.queryParams.QueryParams;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.lmx.common.constants.CommonConstants;
import com.lmx.common.context.ActionContextHolder;
import com.lmx.common.util.CommonUtil;
import com.lmx.persistence.service.common.BaseCrudServiceImpl;
import com.lmx.repoDomain.exception.BusinessException;
import com.lmx.repoDomain.tmodel.Employee;
import com.lmx.repoDomain.tmodel.PersonLocation;
import com.lmx.repoDomain.tmodel.PersonLocationReport;
import com.lmx.repoDomain.tmodel.Room;
import com.lmx.repoDomain.tmodel.Tenant;
import com.lmx.repoDomain.tmodel.WorkTimeSetting;
import com.lmx.repoDomain.tmodel.unpersis.QueryParam;

@Service(value = "personLocationReportService")
public class PersonLocationReportServiceImpl extends
		BaseCrudServiceImpl<PersonLocationReport> implements
		PersonLocationReportService {
	private Log logger = LogFactory.getLog(this.getClass());
	@Autowired
	private PersonLocationService dataSrv;
	@Autowired
	private TenantService<Tenant> tenantSrv;
	@Autowired
	private GenericService dslSrv;
	@Autowired
	private WorkTimeSettingService workTimeSettingSrv;
	private final Integer limit = 3000;// 分页大小,同步时分页查询数据并处理,避免数据过多导致读取太慢
	private boolean firstSysFlag;// 是否第一次同步当天数据

	@Override
	@Transactional
	public Boolean sysLocationReportData(Long dateValue) {
		dateValue = dateValue == null ? new DateTime().minusDays(1).toDate()
				.getTime() : dateValue;
		// 再删除当天旧数据之前判断是否是第一次同步当天数据
		this.firstSysFlag = this.checkFirstSys(dateValue);
		this.deleteOldData(dateValue);
		List<Tenant> tenants = this.tenantSrv.findActiveTenant();
		for (Tenant tenant : tenants) {
			List<PersonLocationReport> reportList = Lists.newArrayList();
			List<WorkTimeSetting> workTimeSettingList = this.workTimeSettingSrv
					.findNotDelete("tenant.id", tenant.getId());
			Integer start = 0;
			Long dataCount = this.dataSrv.getDataCount(dateValue,
					tenant.getId());
			Long loopCount = dataCount % limit == 0 ? dataCount / limit
					: dataCount / limit + 1;
			// 分页同步数据
			for (int i = 0; i < loopCount; i++) {
				List<PersonLocation> list = this.dataSrv.findRowData(dateValue,
						this.limit, start, tenant.getId());
				if (list == null || list.size() < 1) {
					break;
				}
				start += limit;
				Stream<PersonLocation> allDataStream = list.stream().filter(
						(PersonLocation p) -> (p.getEmployee() != null));
				this.sysRoomByCoordinate(allDataStream);
				this.sysRoomData(
						reportList,
						workTimeSettingList,
						dateValue,
						allDataStream.filter((PersonLocation p) -> (p.getRoom() != null)));
			}
			// 保存生成的报表数据
			for (PersonLocationReport report : reportList) {
				if (report.getMinitus() > 0) {
					super.create(report);
				}
			}

		}
		return true;

	}

	@SuppressWarnings("unchecked")
	private boolean checkFirstSys(Long dateValue) {
		Integer startTime = CommonUtil.getDayBegin(new Date(dateValue));
		Integer endTime = CommonUtil.getDayBegin(new DateTime(dateValue)
				.plusDays(1).toDate());
		List<PersonLocationReport> list = this.em
				.createQuery(
						"from PersonLocationReport where generateTime >=:startTime and generateTime<:endTime")
				.setParameter("startTime", startTime)
				.setParameter("endTime", endTime).getResultList();
		return list == null || list.size() < 1;
	}

	@Override
	public Iterable<PersonLocationReport> page(QueryParams requestParams) {
		QueryParam pageParam = new QueryParam(requestParams);
		Map<String, Object> valueMap = Maps.newHashMap();
		StringBuilder sqlSb = new StringBuilder(
				"SELECT * FROM TNB_PERSON_LOCATION_REPORT ");
		StringBuilder countSqlSb = new StringBuilder(
				"SELECT COUNT(*) FROM TNB_PERSON_LOCATION_REPORT");
		StringBuilder conditionSb = new StringBuilder(" WHERE 1=1 ");
		// 处理条件过滤
		Map<String, Object> filterMap = pageParam.getFilter();
		addFilterCondition(valueMap, conditionSb, filterMap);
		
		conditionSb.append(" and tenant_id =  "+ActionContextHolder.getContext().getTenantId());
		// 处理排序
		if (filterMap.containsKey("countWorkTime")
				&& CommonConstants.DEL_STATUS_NO_0.toString().equals(
						filterMap.get("countWorkTime"))) {
			conditionSb.append(" ORDER BY EMPLOYEE_ID,CREATE_TIME,ROOM_ID");
		} else {
			conditionSb
					.append(" ORDER BY EMPLOYEE_ID,CREATE_TIME, WORK_TIME_SET_ID,ROOM_ID");
		}

		sqlSb.append(conditionSb);
		countSqlSb.append(conditionSb);
		// 分页查询
		Page<PersonLocationReport> pageReport = super.pageBySql(
				sqlSb.toString(), countSqlSb.toString(), pageParam, valueMap);
		return pageReport.getContent();
	}

	private PersonLocationReport findCrossDayReport(WorkTimeSetting setting,
			Long generateTime, Long employeeId, Long roomId) {

		Integer startTime = CommonUtil.getDayBegin(new DateTime(generateTime)
				.minusDays(1).toDate());
		Integer endTime = CommonUtil.getDayBegin(new Date(generateTime));
		try {
			String hql = "from PersonLocationReport where wortTimeSetting.id=:settingId "
					+ " and generateTime >= :startTime and generateTime< :endTime "
					+ " and employee.id=:employeeId and room.id=:roomId ";
			return (PersonLocationReport) this.em.createQuery(hql)
					.setParameter("settingId", setting.getId())
					.setParameter("startTime", startTime)
					.setParameter("employeeId", employeeId)
					.setParameter("roomId", roomId)
					.setParameter("endTime", endTime).getSingleResult();
		} catch (Exception e) {
			logger.error("跨天排班信息更新出错,无法找到前一天的数据,");
			logger.error("日期为:" + new Date(generateTime));
			logger.error("排班id为:" + setting.getId());
			logger.error(e);
			return null;
		}

	}

	/**
	 * 处理过滤条件
	 *
	 * @author ytche
	 * @param valueMap
	 *            sql查询的字段以及对应的字段值
	 * @param conditionSb
	 *            sql的条件语句
	 * @param filterMap
	 *            客户端查询的字段以及对应值
	 * @date 2017年6月14日 下午4:28:28
	 */
	private void addFilterCondition(Map<String, Object> valueMap,
			StringBuilder conditionSb, Map<String, Object> filterMap) {
		if (!filterMap.isEmpty()) {
			// 库里的数据是第二天生成的,创建时间比实际时间多一天,所以开始时间/结束时间在查询时需要加上一天时间
			if (filterMap.containsKey("beginTime")) {
				conditionSb.append(" AND CREATE_TIME >= :beginTime ");
				valueMap.put("beginTime", this.getPlusDayBeginTime(new Long(
						filterMap.get("beginTime").toString()), 1));
			}
			if (filterMap.containsKey("endTime")) {
				// 取时间小于结束时间第二天的开始时间,需要在原来的基础上多加一天
				conditionSb.append(" AND CREATE_TIME < :endTime ");
				valueMap.put("endTime", this.getPlusDayBeginTime(new Long(
						filterMap.get("endTime").toString()), 2));
			}
			if (filterMap.containsKey("employee.id")) {
				conditionSb.append(" AND EMPLOYEE_ID =:employeeId ");
				valueMap.put("employeeId", filterMap.get("employee.id"));
			}
			if (filterMap.containsKey("room.id")) {
				conditionSb.append(" AND ROOM_ID =:roomId ");
				valueMap.put("roomId", filterMap.get("room.id"));
			}
			if (filterMap.containsKey("countWorkTime")
					&& CommonConstants.DEL_STATUS_NO_0.toString().equals(
							filterMap.get("countWorkTime"))) {
				conditionSb.append(" AND ISNULL(WORK_TIME_SET_ID) ");
				return;
			}
			conditionSb.append(" AND ALL_DAY_FLAG=0 ");
			if (filterMap.containsKey("wortTimeSetting.id")) {
				conditionSb
						.append(" AND WORK_TIME_SET_ID =:wortTimeSettingId ");
				valueMap.put("wortTimeSettingId",
						filterMap.get("wortTimeSetting.id"));
			}
		}
	}

	/**
	 * 同步前删除当天旧的报表信息,
	 *
	 * @author ytche
	 * @param dateValue
	 * @date 2017年6月19日 下午3:10:54
	 */
	@Transactional
	private void deleteOldData(Long dateValue) {
		Integer startTime = CommonUtil.getDayBegin(new Date(dateValue));
		Integer endTime = CommonUtil.getDayBegin(new DateTime(dateValue)
				.plusDays(1).toDate());
		this.em.createNativeQuery(
				"delete from tnb_person_location_report where generate_time>=:startTime and generate_time<:endTime")
				.setParameter("startTime", startTime)
				.setParameter("endTime", endTime).executeUpdate();
	}

	/**
	 * 处理带有房间信息的数据
	 *
	 * @author ytche
	 * @param reportList
	 * @param workTimeSetList
	 * @param roomDataStream
	 * @date 2017年6月6日 下午4:08:29
	 */
	private void sysRoomData(List<PersonLocationReport> reportList,
			List<WorkTimeSetting> workTimeSetList, Long dateValue,
			Stream<PersonLocation> roomDataStream) {
		// 将位置信息按照员工,房间将信息分组
		Map<Employee, Map<Room, List<PersonLocation>>> employeeMap = roomDataStream
				.collect(Collectors.groupingBy(PersonLocation::getEmployee,
						Collectors.groupingBy(PersonLocation::getRoom)));
		// 统计分好组的位置数据
		for (Entry<Employee, Map<Room, List<PersonLocation>>> employeeEntry : employeeMap
				.entrySet()) {
			for (Entry<Room, List<PersonLocation>> roomEntry : employeeEntry
					.getValue().entrySet()) {
				List<PersonLocation> locations = roomEntry.getValue();
				// 如果没有排班信息,录入时不考虑排班时间
				if (workTimeSetList == null || workTimeSetList.size() < 1) {
					this.sysReportData(employeeEntry.getKey(),
							roomEntry.getKey(), null, locations.size(),
							reportList, dateValue, false);
					continue;
				}
				// 如果有排班信息,按照排班信息统计位置信息
				this.sysRoomDataWithSetting(reportList, workTimeSetList,
						dateValue, employeeEntry, roomEntry, locations);
				// 忽略排班信息统计全天位置信息
				this.sysReportData(employeeEntry.getKey(), roomEntry.getKey(),
						null, locations.size(), reportList, dateValue, true);
			}
		}
	}

	/**
	 * 按照排班信息统计位置信息
	 *
	 * @author ytche
	 * @param reportList
	 * @param workTimeSetList
	 * @param dateValue
	 * @param employeeEntry
	 * @param roomEntry
	 * @param locations
	 * @date 2017年6月19日 下午4:47:17
	 */
	private void sysRoomDataWithSetting(List<PersonLocationReport> reportList,
			List<WorkTimeSetting> workTimeSetList, Long dateValue,
			Entry<Employee, Map<Room, List<PersonLocation>>> employeeEntry,
			Entry<Room, List<PersonLocation>> roomEntry,
			List<PersonLocation> locations) {
		for (WorkTimeSetting workTimeSetting : workTimeSetList) {
			try {
				Integer minites;
				Integer startTime = this.getWorkTimeDate(dateValue,
						workTimeSetting.getStartTimeInt());
				Integer endTime = this.getWorkTimeDate(dateValue,
						workTimeSetting.getEndTimeInt());
				// 如果排班时间跨天,将0点到结束时间的位置信息统计到前一天
				if (endTime < startTime) {
					final Integer crossBegin = CommonUtil.getDayBegin(new Date(
							dateValue));
					final Integer crossEnd = new Integer(endTime);
					minites = (int) locations
							.stream()
							.filter(p -> p.getCreateDateTime() >= crossBegin
									&& p.getCreateDateTime() < crossEnd)
							.count();
					PersonLocationReport report = this.findCrossDayReport(
							workTimeSetting, dateValue, employeeEntry.getKey()
									.getId(), roomEntry.getKey().getId());
					// 如果多次同步,只在第一次同步的时候更新前一天的数据
					if (report != null && this.firstSysFlag) {
						report.setMinitus(report.getMinitus() + minites);
						super.update(report);
					}
					// 如果排班信息跨天,值统计开始时间到24点的位置信息(0点到结束时间的信息第二天统计)
					endTime = CommonUtil.getDayBegin(new DateTime(dateValue)
							.plusDays(1).toDate());
				}
				final Integer begin = new Integer(startTime);
				final Integer end = new Integer(endTime);
				minites = (int) locations
						.stream()
						.filter(p -> p.getCreateDateTime() >= begin
								&& p.getCreateDateTime() < end).count();
				this.sysReportData(employeeEntry.getKey(), roomEntry.getKey(),
						workTimeSetting, minites, reportList, dateValue, false);
			} catch (BusinessException e) {
				logger.error("排班时间为空,排班ID为:" + workTimeSetting.getId());
				continue;
			}
		}
	}

	/**
	 * 根据雇工/房间/排班判断获取暂存程序里对应的报表信息并更新统计数据,如果没有则创建
	 *
	 * @author ytche
	 * @param employee
	 * @param room
	 * @param workTimeSetting
	 * @param minites
	 * @param reportList
	 * @param dateValue
	 *            需要统计的数据的日期
	 * @date 2017年6月6日 下午3:18:11
	 */
	private void sysReportData(Employee employee, Room room,
			WorkTimeSetting workTimeSetting, Integer minites,
			List<PersonLocationReport> reportList, Long dateValue,
			Boolean allDayFlag) {
		if (employee == null || room == null || reportList == null) {
			return;
		}
		// 有排班信息时的全天统计
		Predicate<PersonLocationReport> allDayFilter = p -> employee.getId() == p
				.getEmployee().getId()
				&& room.getId() == p.getRoom().getId()
				&& p.getWortTimeSetting() == null && p.getAllDayFlag() == 1;
		// 无排班信息时的全天统计
		Predicate<PersonLocationReport> noWorkFilter = p -> employee.getId() == p
				.getEmployee().getId()
				&& room.getId() == p.getRoom().getId()
				&& p.getWortTimeSetting() == null && p.getAllDayFlag() == 0;
		// 有排班信息时的排班信息统计
		Predicate<PersonLocationReport> withWorkFilter = p -> employee.getId()
				.equals(p.getEmployee().getId())
				&& room.getId().equals(p.getRoom().getId())
				&& p.getWortTimeSetting() != null
				&& p.getWortTimeSetting().getId()
						.equals(workTimeSetting.getId());
		// 查询报表统计list是否有符合条件的对象
		Optional<PersonLocationReport> reportOptional = reportList
				.stream()
				.filter(workTimeSetting == null ? (allDayFlag ? allDayFilter
						: noWorkFilter) : withWorkFilter).findFirst();
		PersonLocationReport report;
		if (reportOptional.isPresent()) {
			report = reportOptional.get();
			report.setMinitus(report.getMinitus() + minites);
		} else {
			report = new PersonLocationReport();
			report.setTenant(employee.getTenant());
			report.setRoom(room);
			report.setEmployee(employee);
			report.setWortTimeSetting(workTimeSetting);
			report.setMinitus(minites);

			report.setGenerateTime(new Long(dateValue / 1000).intValue());
			report.setAllDayFlag(allDayFlag ? 1 : 0);
			reportList.add(report);
		}
	}

	/**
	 * 根据排班起始时间获取当天排班的起始时间
	 *
	 * @author ytche
	 * @param dateValue
	 * @param workTimeSetting
	 * @return 当天排班时间的起始时间 时间戳/1000
	 * @date 2017年6月7日 下午1:50:55
	 */
	public Integer getWorkTimeDate(Long dateValue, Integer workTimeSettingTime) {
		if (workTimeSettingTime == null) {
			throw new BusinessException("", "", "排班时间为空");
		}
		Calendar dataDate = Calendar.getInstance();
		Calendar workDate = Calendar.getInstance();
		workDate.setTime(new Date(workTimeSettingTime.longValue() * 1000));
		dataDate.setTime(new Date(dateValue));
		workDate.set(Calendar.YEAR, dataDate.get(Calendar.YEAR));
		workDate.set(Calendar.MONTH, dataDate.get(Calendar.MONTH));
		workDate.set(Calendar.DATE, dataDate.get(Calendar.DATE));
		return new Long(workDate.getTimeInMillis() / 1000).intValue();
	}

	/**
	 * 根据时间获取后N天0点时间
	 *
	 * @author ytche
	 * @param timeValue
	 * @return
	 * @date 2017年6月12日 下午2:29:28
	 */
	private Integer getPlusDayBeginTime(Long timeValue, Integer plusDayValue) {
		Long timeValueLong = timeValue.longValue();
		Date day = new DateTime(timeValueLong).plusDays(plusDayValue).toDate();
		return CommonUtil.getDayBegin(day);
	}

	/**
	 * 针对没有room的记录,根据经纬度信息生成room
	 *
	 * @author ytche
	 * @param allDataStream
	 * @date 2017年6月6日 下午2:13:16
	 */
	private void sysRoomByCoordinate(Stream<PersonLocation> allDataStream) {
		// TODO 通过经纬度确定房间
		// allDataStream.filter(p -> p.getRoom() ==
		// null).forEach(p->p.setRoom(??));;
	}

}
